﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FreeSql;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace FreeSqlExtensions
{
    public static class FreeSqlSetup
    {
        //注入方式配置FreeSql。本框架暂时不用此注入
        /*
        public static void AddFreeSqlSetup(this IServiceCollection services)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
            
            services.AddSingleton<IFreeSql>(o =>
            {
                return new FreeSql.FreeSqlBuilder()
                    .UseConnectionString(GetDbType(), _ConnectionString)
                    .UseAutoSyncStructure(true) //自动同步实体结构到数据库
                    .Build(); //请务必定义成 Singleton 单例模式
            });
        }

        private static string _ConnectionString => Appsettings.app(new string[] { "AppSettings", "DbConnection", "ConnectionString" });
        private static FreeSql.DataType GetDbType()
        {
            string dbType = Appsettings.app(new string[] { "AppSettings", "DbConnection", "DbType" });
            switch (dbType.ToLower())
            {
                case "postgresql":
                case "pgsql":
                    return FreeSql.DataType.PostgreSQL;
                case "sqlite":
                    return FreeSql.DataType.Sqlite;
                case "oracle":
                    return FreeSql.DataType.Oracle;
                case "mysql":
                    return FreeSql.DataType.MySql;
                default://默认为mssql
                    return FreeSql.DataType.SqlServer;
            }
        }
        */

        #region FreeSql AOP
        /// <summary>
        /// 初始化aop
        /// </summary>
        /// <param name="fsql"></param>
        /// <param name="logger"></param>
        /// <param name="DbName"></param>
        public static void FreeSqlAop(this IFreeSql fsql, ILogger logger,string DbName="")
        {
            //var fsql = FreeSqlHelper.Fsql;

            //全局帮助实现“软删除”、“租户”等设计 ，参考 https://github.com/dotnetcore/FreeSql/wiki/%E8%BF%87%E6%BB%A4%E5%99%A8


            //按CurdBefore CommandBefore CommandAfter CurdAfter 顺序执行。 CommandAfter 会监听所有执行的sql，CurdAfter不能监听ado相关执行 sql(只监听lambda)。

            //fsql.Aop.CurdBefore += (s, args) => { };

            fsql.Aop.CommandBefore += (s, args) =>
            {
#if DEBUG
                //输出到控制台显示SQL
                var parInfo = LookSQL(args.Command.CommandText, args.Command.Parameters);

                logger.Log(LogLevel.Information,System.Environment.NewLine
                                       + "-----------------------------------sql-----------------------------------" + System.Environment.NewLine
                                    +"["+DbName + "] =>>" + parInfo.Item1 + System.Environment.NewLine
                                       + "-----------------------------------sql-----------------------------------" + System.Environment.NewLine
                                    );
#endif
            };

            fsql.Aop.CommandAfter += (s, args) =>
            {
#if DEBUG
                //获取执行后的总毫秒数
                long sqlExecutionTotalMilliseconds = args.ElapsedMilliseconds;
#endif

                if (args.Exception != null)
                {
                    StringBuilder sb_ParameterStr = new StringBuilder("###DbParms参数为:");

                    var parInfo = LookSQL(args.Command.CommandText, args.Command.Parameters);

                    sb_ParameterStr.Append(parInfo.Item2 != null ? JsonConvert.SerializeObject(parInfo.Item2) : "null");

                    StringBuilder sb_error = new StringBuilder();
                    sb_error.AppendLine("["+DbName+"]FreeSqlClient执行sql异常信息:" + args.Exception.Message);
                    sb_error.AppendLine("###赋值后sql:" + parInfo.Item1);
                    sb_error.AppendLine("###带参数的sql:" + args.Command.CommandText);
                    sb_error.AppendLine("###参数信息:" + sb_ParameterStr.ToString());
                    sb_error.AppendLine("###StackTrace信息:" + args.Exception.StackTrace);

                    logger.Log(LogLevel.Error,sb_error.ToString());
                }

            };

            //参考：https://github.com/dotnetcore/FreeSql/wiki/AOP

            //fsql.Aop.ParseExpression += (s, e) => {
            //    if (e.Expression.NodeType == Call && e.Expression.Name == "get_Item")
            //        e.Result = "1111";
            //};

            //fsql.Aop.AuditValue += (s, e) => {
            //    if (e.Column.CsType == typeof(long) //freesql自带的雪花特性
            //        && e.Property.GetCustomAttribute<SnowflakeAttribute>(false) != null
            //        && e.Value?.ToString() == 0)
            //        e.Value = new Snowflake().GetId();
            //};


            if (fsql.Ado.DataType == DataType.Oracle)
            {
                fsql.Aop.AuditDataReader += (obj, ae) =>
                {
                     //处理oracle特有的空字符串存储为null的问题，查询时候string默认null转为空字符串
                     //https://github.com/dotnetcore/FreeSql/issues/436

                     if (ae.DataReader.GetFieldType(ae.Index) == typeof(string) && ae.Value == DBNull.Value)
                        ae.Value = string.Empty;
                };
            }



            fsql.Aop.ConfigEntityProperty += (s, ce) =>
            {
                //默认情况 c# 枚举会映射为 MySql Enum 类型，如果想映射为 int 在 FreeSqlBuilder Build 之后执行以下 Aop 统一处理。https://github.com/dotnetcore/FreeSql/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
                // if (fsql.Ado.DataType == DataType.MySql && ce.Property.PropertyType.IsEnum)
                //     ce.ModifyResult.MapType = typeof(int);

                //为了LookSQL能正常显示Enum特性的值
                if (ce.Property.PropertyType.IsEnum)
                    ce.ModifyResult.MapType = typeof(int);
            };
        }


        /// <summary>
        /// 查看赋值后的sql和参数集合
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="parameterCollection">参数</param>
        /// <returns></returns>
        public static (string, DbParameter[]) LookSQL(string sql, DbParameterCollection parameterCollection)
        {
            if (parameterCollection == null || parameterCollection.Count == 0) return (sql, null);

            DbParameter[] pars = new DbParameter[parameterCollection.Count];
            for (var i = 0; i < parameterCollection.Count; i++)
            {
                pars[i] = parameterCollection[i];
            }

            StringBuilder sb_sql = new StringBuilder(sql);
            var tempOrderPars = pars.Where(p => p != null && p.Value != null)
                .OrderByDescending(p => p.ParameterName.Length).ToList();//防止 @par1错误替换@par12

            for (var index = 0; index < tempOrderPars.Count; index++)
            {
                sb_sql.Replace(tempOrderPars[index].ParameterName, "'" + tempOrderPars[index].Value.ToString() + "'");
            }

            return (sb_sql.ToString(), pars);
        }
        #endregion
    }
}
